#include <stdio.h>
#include <assert.h>

#include "canvas.h"
#include "bitmap.h"
#include "grphoal.h"
#include "canvas_impl.h"

namespace visgrph
{

using namespace grphoal;
syncobject g_canvas_syncobj;
lcd_distribution_lut s_lut(3./9., 2./9., 1./9.);

canvas::canvas()
: impl(NULL)
{
}

canvas::~canvas()
{
	if (impl)
	{
		delete impl;
		impl = NULL;
	}
}

canvas::canvas(const canvas& c)
: impl(NULL)
{
	if (!c.impl) return;
	impl = new canvas_impl(*(c.impl));
	assert(NULL != impl);
}

canvas::canvas(const canvas& c, int dx, int dy, int w, int h)
: impl(NULL)
{
	if (!c.impl) return;
	impl = new canvas_impl(*(c.impl), dx, dy, w, h);
	assert(NULL != impl);
}

canvas::canvas(const bitmap& b)
: impl(NULL)
{
	impl = new canvas_impl(b.impl);
	assert(NULL != impl);
}

canvas::canvas(const bitmap& b, int dx, int dy, int w, int h)
: impl(NULL)
{
	impl = new canvas_impl(b.impl, dx, dy, w, h);
	assert(NULL != impl);
}
canvas& canvas::operator=(const canvas& c)
{
	if (this != &c)
	{
		if (impl)
		{
			delete impl;
			impl = NULL;
		}
		if (!c.impl) return *this;
		impl = new canvas_impl(*(c.impl));
		assert(NULL != impl);
	}
	return *this;
}

canvas::canvas(uint width, uint height)
: impl(NULL)
{
	if (!width || !height)
		return;

	bitmap_impl *bmp = new bitmap_impl();
	if (bmp)
	{
		if (bmp->create(width, height, ((width * 4) + 15) & ~15))
		{
			impl = new canvas_impl();
			if (impl) impl->attach(bmp, 0, 0, bmp->width(), bmp->height());
		}
		bmp->release();
	}
}

bool canvas::select_object(const pen& p)
{
	if (!impl) return false;
	impl->select_object(*p.m_pen);
	return true;
}

bool canvas::reset_clipping(void)
{
	if (!impl) return false;
	impl->reset_clipping();
	return true;
}

bool canvas::add_clipbox(int left, int top, int width, int height)
{
	if (!impl) return false;
	return impl->add_clipbox(left, top, width, height);
}

bool canvas::add_clipbox(const rect& r)
{
	if (!impl) return false;
	return impl->add_clipbox(r);
}

void canvas::hline(uint x, uint y, uint length, color c)
{
	if (NULL == impl)
		return;
	impl->hline(x, y, length, c);
}

void canvas::vline(uint x, uint y, uint height, color c)
{
	if (NULL == impl)
		return;
	impl->vline(x, y, height, c);
}

void canvas::rectangle(uint x, uint y, uint width, uint height, color c)
{
	if (NULL == impl)
		return;
	impl->rectangle(x, y, width, height, c);
}

void canvas::box(uint x, uint y, uint width, uint height, color c)
{
	if (NULL == impl || width < 3 || height < 3)
		return;

	hline(x, y, width, c);
	hline(x, y + height - 1, width, c);
	vline(x, y + 1, height - 2, c);
	vline(x + width - 1, y + 1, height - 2, c);
}

void canvas::line(float x1, float y1, float x2, float y2)
{
	if (NULL == impl)
		return;
	impl->line(x1, y1, x2, y2);
}

void canvas::clear(color c)
{
	if (NULL == impl)
		return;
	impl->clear(c);
}

int canvas::width(void) const
{
	if (NULL == impl)
		return -1;
	return impl->width();
}

int canvas::height(void) const
{
	if (NULL == impl)
		return -1;
	return impl->height();
}

void canvas::textout(float x, float y, formatted_text text, color c, int sz)
{
	if (NULL == impl)
		return;
	if (sz < 0 && sz > -10000) sz = -10000;
	impl->textout(x, y, text._impl(), c, sz);
}

// implementation of canvas_impl

canvas_impl::canvas_impl(const canvas_impl& c)
: m_target(NULL)
{
	if (this != &c)
	{
		autosync sync(g_canvas_syncobj);
		bitmap_impl* bmp = c.m_target;
		bmp->addref();

		m_target = bmp;
		m_rbuf.attach((agg::int8u*)c.m_rbuf.buf(), c.m_rbuf.width(),
			c.m_rbuf.height(), c.m_rbuf.stride());

		m_pf.attach(m_rbuf);
		m_rmc.attach(m_pf);

		m_pf_lcd.attach(m_rbuf, s_lut);
		m_ren_base_lcd.attach(m_pf_lcd);
		m_ren_solid_lcd.attach(m_ren_base_lcd);
	}
}

bool canvas_impl::attach(canvas_impl& cvs, int dx, int dy, int w, int h)
{
	if (this == &cvs)
		return false;

	bitmap_impl* bmp = cvs.m_target;
	bmp->addref();

	// sanity check
	int c = dx + w, d = dy + h;
	if (dx < 0 || dy < 0 || (uint)dx >= cvs.m_rbuf.width()
		|| (uint)dy >= cvs.m_rbuf.height() || w <= 0
		|| h <= 0 || c < 0 || d < 0 || (uint)c > cvs.m_rbuf.width()
		|| (uint)d > cvs.m_rbuf.height())
	{
		bmp->release();
		return false;
	}

	if (m_target) m_target->release();
	m_target = bmp;

	int stride = cvs.m_rbuf.stride();
	if (stride >= 0)
		m_rbuf.attach(cvs.m_rbuf.buf() + dy * stride + dx * 4,	// todo: ifdef rgba
			w, h, stride);
	else
		m_rbuf.attach(cvs.m_rbuf.buf() - (cvs.m_rbuf.height() - dy - h) * stride + dx * 4,	// todo: ifdef rgba
			w, h, stride);

	m_pf.attach(m_rbuf);
	m_rmc.attach(m_pf);

	m_pf_lcd.attach(m_rbuf, s_lut);
	m_ren_base_lcd.attach(m_pf_lcd);
	m_ren_solid_lcd.attach(m_ren_base_lcd);
	return true;
}

bool canvas_impl::attach(bitmap_impl* target, int dx, int dy, int w, int h)
{
	if (NULL == target)
		return false;

	autosync sync(g_canvas_syncobj);
	target->addref();

	// sanity check
	int c = dx + w, d = dy + h;
	if (dx < 0 || dy < 0 || (uint)dx >= target->width()
		|| (uint)dy >= target->height() || w <= 0
		|| h <= 0 || c < 0 || d < 0 || (uint)c > target->width()
		|| (uint)d > target->height())
	{
		target->release();
		return false;
	}

	// release the previous one, if we have
	if (m_target) m_target->release();
	m_target = target;

	int stride = target->stride();
	if (stride >= 0)
		m_rbuf.attach(((agg::int8u*)target->buffer()) + dy * stride + dx * 4,	// todo: ifdef rgba
			w, h, target->stride());
	else
		m_rbuf.attach(((agg::int8u*)target->buffer()) - (target->height() - dy - h) * stride + dx * 4,	// todo: ifdef rgba
			w, h, target->stride());

	m_pf.attach(m_rbuf);
	m_rmc.attach(m_pf);

	m_pf_lcd.attach(m_rbuf, s_lut);
	m_ren_base_lcd.attach(m_pf_lcd);
	m_ren_solid_lcd.attach(m_ren_base_lcd);
	return true;
}

}; // namespace visgrph
/* EOF */
